/** \file    refine/grey_mask.cpp
  * \brief   Moves across the mask bounds and adds or removes single pixels to it 
  *			 or marks pixels with grey color as uncertain, according to color ditribution in image.
  * \date    2003
  * \author  Fedor Chelnokov
  * \version $Header: /home/cscom1r4/cvsroot/cs/refine/grey_mask.cpp,v 1.1 2004/02/04 14:43:24 cscom1r4 Exp $
  */

#include "refine/grey_mask.h"
#include "refine/topology_limit.h"
#include "imglib/imageclass.h"
#include "imglib/bitmap.h"
#include <iostream>
#include <limits>
#include <xtd/minmax>
using std::clog;
using std::endl;

namespace refine {

namespace grey_mask {

const params default_params = {6, 3, 7, true, true, false, false};

const mask_value mask_max_value = std::numeric_limits<mask_value>::max();
const mask_value mask_min_value = std::numeric_limits<mask_value>::min();
const mask_value mask_avg_value = (mask_max_value + mask_min_value) / 2;

template <class M>
class refiner {
public:
	refiner(const params & p, const byte_image & s, M & m) 
		: _params(p), _src(s), _mask(&m) { }
	void run();

	const byte_image & src() const
		{ return _src; }
	const byte_color & src(size_point p) const
		{ return _src[p]; }
	const byte_color & src(unsigned x, unsigned y) const
		{ return _src(x,y); }
	unsigned width() const
		{ return _src.width(); }
	unsigned height() const
		{ return _src.height(); }
	size_extent dim() const
		{ return _src.dim(); }

	const M & m() const
		{ return *_mask; }
	mask_value m(size_point p) const
		{ return (*_mask)[p]; }
	mask_value m(unsigned x, unsigned y) const
		{ return (*_mask)(x,y); }

	mask_value dev_mask(size_point p) const
		{ return _params.self_development ? m(p) : _src_mask[p]; }
private:
	///\return true if a change was made
	bool test(size_point p);
private:
	const params & _params;
	const byte_image & _src;
	mask _src_mask;
	M * _mask;
	int_rect _frame;
};

template <class M>
void refine0(const byte_image & src, M & m, const params & p)
{
	refiner<M>(p, src, m).run();
}

void refine(const byte_image & src, mask & m, const params & p)
{
	refine0(src, m, p);
}

void refine(byte_image & img, const params & p)
{
	refine0(img, img::projection(img, &byte_color::a), p);
}

template <class M>
void refiner<M>::run()
{
	assert(_mask->dim() == dim());
	_frame = int_rect(0, 0, width(), height());

	if (_params.self_development)
		_src_mask.clear();
	else
		_src_mask = *_mask;

	img::bitmap layer1(dim()), layer2(dim());
	layer1.fill(true);
	img::bitmap * next = &layer2;
	img::bitmap * curr = &layer1;

	if (!_params.silent)
		clog << "refine grey mask initiated..." << endl;

	for (int pass = 0; pass < 150; ++pass, std::swap(next, curr)) {
		unsigned tests = 0, changes = 0;

		next->fill(false);
		img::bitmap::iterator i;
		for (i = curr->begin(); i != curr->end(); ++i) {
			if (!*i)
				continue;
			*i = false;
			size_point p = i;
			if (p.x == 0 || p.y == 0 || p.x == width()-1 || p.y == height()-1)
				continue;
			++tests;
			if (test(p)) {
				++changes;
				if (_params.minimize_test_count) {
					(*next)[p - size_point(1,0)] = true;
					(*next)[p - size_point(0,1)] = true;
					(*curr)[p + size_point(1,0)] = true;
					(*curr)[p + size_point(0,1)] = true;
				}
				else {
					int_point d, z;
					for (d.y = -_params.window_half_size; d.y <= 0; ++d.y)
						for (d.x = -_params.window_half_size; (d.y < 0) && d.x <= _params.window_half_size || d.x < 0; ++d.x) 
							if (_frame.inside(z = int_point(p) + d))
								(*next)[z] = true;
					for (d.y = 0; d.y <= _params.window_half_size; ++d.y)
						for ((d.y == 0) ? (d.x = 1) : (d.x = -_params.window_half_size); d.x <= _params.window_half_size; ++d.x) 
							if (_frame.inside(z = int_point(p) + d))
								(*curr)[z] = true;
				}
			}
		}

		assert(curr->set_bit_count() == 0);

		if (!_params.silent)
			clog << "pass " << pass <<
					": tests=" << tests << 
					", changes=" << changes << 
					endl;

		if (changes == 0)
			break;
	}

	if (!_params.silent)
		clog << endl;
}

template <class M>
bool refiner<M>::test(size_point p)
{
	M & m = *_mask;

	/*mask_value min_nei = mask_max_value, max_nei = mask_min_value;
	if (p.x != 0)
		xtd::minmax(min_nei, max_nei, m(p.x-1, p.y));
	if (p.y != 0)
		xtd::minmax(min_nei, max_nei, m(p.x, p.y-1));
	if (p.x != width()-1)
		xtd::minmax(min_nei, max_nei, m(p.x+1, p.y));
	if (p.y != height()-1)
		xtd::minmax(min_nei, max_nei, m(p.x, p.y+1));*/

	mask_value min_nei, max_nei;
	topology_limit(m, p, min_nei, max_nei);
	if (min_nei > max_nei)
		return false;

	mask_value v0 = m[p];

	if (min_nei == max_nei) {
		if (v0 != min_nei) {
			m[p] = min_nei;
			return true;
		}
		return false;
	}

	if (!_params.soft_mask && min_nei != mask_min_value && max_nei != mask_max_value)
		return false;

	typedef std::pair<unsigned, mask_value> similarity_value_pair;
	std::vector<similarity_value_pair> votes;

	typedef img::BGRA<unsigned> ucolor;
	ucolor centre = _src[p];

	int_point d;
	for (d.y = -_params.window_half_size; d.y <= _params.window_half_size; ++d.y) {
		for (d.x = -_params.window_half_size; d.x <= _params.window_half_size; ++d.x) 
		{
			if (d.x == 0 && d.y == 0)
				continue;
			int_point z = int_point(p) + d;
			if (!_frame.inside(z))
				continue;
			size_point uz = z;

			votes.push_back(
				similarity_value_pair((centre - ucolor(_src[uz])).sqr(), dev_mask(uz)));
		}
	}

	std::sort(votes.begin(), votes.end());

	//remove "tail"
	if (votes.back().second == mask_min_value) {
		while (votes.size() > _params.enough_statistics && votes.back().second == mask_min_value)
			votes.pop_back();
	}
	else if (votes.back().second == mask_max_value) {
		while (votes.size() > _params.enough_statistics && votes.back().second == mask_max_value)
			votes.pop_back();
	}

	float lo = 0, hi = 0;
	float domin = 0;
	mask_value vx;
	unsigned n;
	for (n = 0; n != votes.size(); ++n) {
		mask_value v = votes[n].second;
		if (v > mask_avg_value)
			hi += v - mask_avg_value;		//v-127
		else
			lo += mask_avg_value - v + 1;	//128-v
		//lo += mask_max_value - v;
		//hi += v;

		float hi_domin = lo == 0 ? _params.predominance : hi / lo;
		float lo_domin = hi == 0 ? _params.predominance : lo / hi;

		if (n >= _params.enough_statistics) {
			if (hi_domin >= _params.predominance) {
				vx = mask_max_value;
				break;
			}
			if (lo_domin >= _params.predominance) {
				vx = mask_min_value;
				break;
			}
			if (hi_domin > lo_domin && hi_domin > domin) {
				domin = hi_domin;
				vx = linear(mask_avg_value, mask_max_value, domin / _params.predominance);
			}
			else if (lo_domin > domin) {
				domin = lo_domin;
				vx = linear(mask_avg_value, mask_min_value, domin / _params.predominance);
			}
		}
	}

	xtd::max(vx, min_nei);
	xtd::min(vx, max_nei);

	if (v0 == vx)
		return false;

	if (!_params.soft_mask && (
			min_nei != mask_min_value && vx != mask_max_value ||
			max_nei != mask_max_value && vx != mask_min_value))
		return false;

	m[p] = vx;
	return true;
}

} //namespace grey_mask

} //namespace refine
